/**
 ******************************************************************************
 * @file app_snake.c
 * @author GX (2524913364@qq.com)
 * @brief 
 * @version 1.0.0 
 * @date 2022-05-26
 * 
 * @copyright Copyright (c) 2022
 * 
 ******************************************************************************
 *----------------------------------------------------------------------------*
 *  Remark         : Description                                              *
 *----------------------------------------------------------------------------*
 *  Change History :                                                          *
 *  <Date>     | <Version> | <Author>       | <Description>                   *
 *----------------------------------------------------------------------------*
 *  2022-05-26 | 1.0.0     | GX             | Create file                     *
 *----------------------------------------------------------------------------*
 *                                                                            *
 ******************************************************************************
 */


/*----------------------------- start of file -------------------------------*/
#include "app_snake.h"
#include "app_snake_file.h"
#include "lcd.h"
#include "fonts.h"
#include "touch_screen.h"

/* 静态的函数声明--------------------------------------------------------- */
static void snake_init(void);
static void snake_refreshInterface (int *fb_base, int fd,
                                      struct fb_var_screeninfo *vinfo);
static DCLinkList_t *snake_move (DCLinkList_t *snake_player);
static inline void grassToMap (int32_t b_map_x, int32_t b_map_y);
static SnakeKey_t getKey (int32_t x, int32_t y);
static void generate_goods (SBlockType_t goods);
static DCLinkList_t *eat_something(datatype_t snakeHead_data, DCLinkList_t *snake);
static void trigger_block(DCLinkList_t *snake, SnakePlayer_t *py, DCLinkList_t *eatBlock);
static SI_KEY snake_backOrRestart_interface (int *fb_base, int fd,
                                                struct fb_var_screeninfo *vinfo);

/* 全局变量定义---------------------------------------------------------- */
static bool back    = false;
static bool bgmON   = false;              /* 背景音乐是否打开 */
static bool bgmHasON = false;             /* 背景音乐是否已经打开 */
static bool suspend = false;              /* 游戏是否暂停 */
static int32_t b_view_x;                  /* 视野的 x 方块坐标 */
static int32_t b_view_y;                  /* 视野的 y 方块坐标 */
static int32_t *mapbuf;                   /* 整个地图的图像，包括蛇在内的所有方块的图像 */
static int32_t *grassbuf1;                /* 草地方块的图像缓存，因为要经常用到 */
static int32_t *grassbuf2;                /* 草地方块的图像缓存，因为要经常用到 */
static DCLinkList_t *blockGroud_list[4];  /* 4个区块的物品链表 */
static DCLinkList_t *snake[1];            /* 玩家的链表 */

static Snake_Iinterface_t  game_interface;

/* 方块的属性变量 */
static datatype_t food;
static datatype_t poison;
static datatype_t wall;
static datatype_t snake_head;
static datatype_t snake_body;
static datatype_t snake_tail;


bool app_snake(void)
{
  printf("Into app_snake\n");
  /*------------------------------开启显示屏幕-----------------------------*/
  int fd_lcd;                             /* 打开文件获取文件描述 */
  int *fb_base = NULL;                    /* 映射后显存的地址 */
  struct fb_var_screeninfo vinfo;         /* 显卡设备的可变属性结构体 */
	/* 打开文件 */
	fd_lcd = open(LCD_PATH, O_RDWR);
	if(fd_lcd == -1)
	{
		perror("open lcd");
    return false;
	}
  else
	{
		/* 显存映射 */
		fb_base = mmap(NULL, LCD_BUFF_SIZE*LCD_BLOCK,  PROT_READ|PROT_WRITE,
					MAP_SHARED, fd_lcd, 0);
		if(fb_base == MAP_FAILED) 
		{
			perror("申请显存失败");
      close(fd_lcd);
      return false;
		} 

    if(ioctl(fd_lcd, FBIOGET_VSCREENINFO, &vinfo) != 0)/* 获取可变属性 */
    {
        perror("获取LCD设备可变属性信息失败");
        return false;
    }
  }

  /*-------------------------------正式开始贪吃蛇相关----------------------------*/
  /* 先进入游戏介绍的界面---------------------------- */
  snake_intro_interface (fb_base, fd_lcd, &vinfo);
  
  /* 显示字符相关配置------------------------------- */
  /* 要先把进来前的字体配置记住，在结束后要设置回来 */
  bool useBack;
  u_int32_t TextColor, BackColor;
  sFONT *fonts;
  fonts = LCD_GetFont();
  LCD_GetColors(&TextColor, &BackColor, &useBack);
  LCD_SetFont(&SNAKE_FONT); 
  LCD_SetColors(SNAKE_TEXT_COLOR, SNAKE_BACK_COLOR, SNAKE_UNUSE_BACKCOLOR);

  /* 初始化游戏----------------------------------- */
  printf("ready the game\n");
  
  DCLinkList_t *eatBlock = NULL;

  SI_KEY Key = SI_KEY_NONE;
  
  TOUCH *tc = malloc(sizeof(TOUCH));
  SnakeKey_t key[1] = {SNAKE_KEY_NONE};

RESTART:
  /* 初始化一些属性 */
  srand(time(NULL));
  snake_init();

  /* 刷新界面 */
  snake_refreshInterface(fb_base, fd_lcd, &vinfo);

  while(1)
  {
    get_tcState(tc);
    if (!tc->response)
    {
      switch (tc->state)
      {
      case STATE_SINGLE_CLICK:
        // printf("snake STATE_SINGLE_CLICK\n");
        for (int8_t i = 0; i < player_num; i++)
        {
          key[i] = getKey(tc->x_last, tc->y_last);
          printf("key = %d\n", key[i]);

          switch (key[i])
          {
          case SNAKE_KEY_UP:    /* 蛇头方向向上 */
            if (snake[i]->next->data->direction != SNAKE_KEY_DOWN)
            {
              snake[i]->next->data->direction = SNAKE_KEY_UP;
            }
            key[i] = SNAKE_KEY_NONE;
            break;

          case SNAKE_KEY_DOWN:  /* 蛇头方向向下 */
            if (snake[i]->next->data->direction != SNAKE_KEY_UP)
            {
              snake[i]->next->data->direction = SNAKE_KEY_DOWN;
            }
            key[i] = SNAKE_KEY_NONE;
            break;

          case SNAKE_KEY_LEFT:  /* 蛇头方向向左 */
            if (snake[i]->next->data->direction != SNAKE_KEY_RIGHT)
            {
              snake[i]->next->data->direction = SNAKE_KEY_LEFT;
            }
            key[i] = SNAKE_KEY_NONE;
            break;

          case SNAKE_KEY_RIGHT: /* 蛇头方向向右 */
            if (snake[i]->next->data->direction != SNAKE_KEY_LEFT)
            {
              snake[i]->next->data->direction = SNAKE_KEY_RIGHT;
            }
            key[i] = SNAKE_KEY_NONE;
            break;
          
          case SNAKE_KEY_BGM:
            bgmON ^= 1;
            
            key[i] = SNAKE_KEY_NONE;
            break;
          
          case SNAKE_KEY_SUSPEND:
            suspend ^= 1;
            key[i] = SNAKE_KEY_NONE;
            break;

          case SNAKE_KEY_BACK:
            back = true;
            key[i] = SNAKE_KEY_NONE;  
            break;
          
          default:
            key[i] = SNAKE_KEY_NONE;
            break;
          }
        }
        response_over();
        break;
      
      default:
        printf("snake default\n");
        response_over();
        break;
      }
    }

    if (back)
    {
      back = false;
      suspend = true;
      Key = snake_backOrRestart_interface(fb_base, fd_lcd, &vinfo);
      if ((Key == SI_KEY_RESTART) || (Key == SI_KEY_EXIT))
      {
        break;
      }
      Key = SI_KEY_NONE;
    }

    /* 判断音乐是否播放 */
    if ((bgmON) && (!bgmHasON)) /* 希望开音乐且音乐还没开 */
    {
      bgmHasON = true;
      char music_movement[128];
      sprintf(music_movement, "%s%s%s", "madplay ",SNAKE_BGM_PATH," -r &");
      printf("%s\n", music_movement);
      system((const char*)music_movement);
    }
    else if ((!bgmON) && (bgmHasON))  /* 希望关掉音乐且音乐已经打开 */
    {
      bgmHasON = false;
      system("killall -9 madplay &");
    }

    if (!suspend) /* 游戏暂停 */
    {
      for (int8_t i = 0; i < player_num; i++)
      {
        eatBlock = snake_move(snake[i]);
        if (eatBlock != NULL)
        {
          printf("In main fun\n");
          show_all(*(eatBlock->data));
        }
        trigger_block(snake[i], &player[i], eatBlock);  /* 响应方块的触发效果 */
        if (player[i].health == 0)
        {
          printf("player %d death game over\n", i+1);
          back = true;
        }
        eatBlock = NULL;  /* 重置 */
      }
    }
    
    /* 刷新界面 */
    snake_refreshInterface(fb_base, fd_lcd, &vinfo);
    
    usleep(150000);
    
  }

  /* 释放空间 */
  free(mapbuf);
  mapbuf = NULL;
  free(grassbuf1);
  grassbuf1 = NULL;
  free(grassbuf2);
  grassbuf2 = NULL;
  for (int8_t i = 0; i < 4; i++)
  {
    destoryLink(blockGroud_list[i]);
  }
  for (int8_t i = 0; i < player_num; i++)
  {
    destoryLink(snake[i]);
  }

  if (Key == SI_KEY_RESTART)
  {
    Key = SI_KEY_NONE;
    goto RESTART;
  }
    
  system("killall -9 madplay &");
  /* 恢复字体设置 */
  LCD_SetFont(fonts); 
  LCD_SetColors(TextColor, BackColor, useBack);

  view_block = 0;
  free(tc);
  munmap(fb_base, LCD_BUFF_SIZE*LCD_BLOCK);
  close(fd_lcd);

  printf("Out app_snake\n");

  return true;
}

/*******************************************************************************
 * 
 *                                  游戏初始化
 * 
*******************************************************************************/

/**
 * @brief 将方块的图像放到地图图像中
 * 
 * @param map     [out] 地图图像的缓存
 * @param block   方块图像的缓存，尺寸都是 32*32
 * @param b_map_x 方块放在地图中的 方块x坐标
 * @param b_map_y 方块放在地图中的 方块y坐标
 */
static void blockToMap(int32_t *map, int32_t *block, int32_t b_map_x, int32_t b_map_y)
{
  u_int32_t map_line = b_map_y*SNAKE_BLOCK_HEIGHT;  /* 将方块坐标换算成实际坐标 */
  u_int32_t map_row  = b_map_x*SNAKE_BLOCK_WIDTH;   /* 将方块坐标换算成实际坐标 */
  int32_t *mapbuf_pos = map + (map_line*SNAKE_MAP_WIDTH);
  for (u_int32_t line = 0; line < SNAKE_BLOCK_HEIGHT; line++)
  {
    for (u_int32_t row = 0; row < SNAKE_BLOCK_WIDTH; row++)
    {
      if(*(block + (line*SNAKE_BLOCK_WIDTH + row)) != LCD_WHITE)
        *(mapbuf_pos + (line*SNAKE_MAP_WIDTH + map_row + row)) = 
                            *(block + (line*SNAKE_BLOCK_WIDTH + row));
    }
  }
}

/**
 * @brief 刚进入游戏前的初始化
 * 
 */
static void snake_init(void)
{
  DCLinkList_t *node_tmp;
  datatype_t    data_tmp;

  bgmON   = false;
  bgmHasON = false;
  suspend = true;

  /* 游戏界面结构体-------------------------------- */
  game_interface.b_width = 30;
  game_interface.b_height = 30;
  game_interface.b_viewWidth = 15;
  game_interface.b_viewHeight = 15;
  game_interface.b_viewStart_x = 7;
  game_interface.b_viewStart_x = 7;

  /* 玩家属性结构体-------------------------------- */
  for (int32_t i = 0; i < player_num; i++)
  {
    player[i].health = 3;
    memcpy(player[i].name, "Tom", sizeof("Tom"));
    player[i].length = 3;
    player[i].score  = 15;
  }

  /* 将草地图片取出 */
  grassbuf1 = malloc(SNAKE_PIC_WIDTH*SNAKE_PIC_HEIGHT*4);
  lcd_bmp_to_Buf(grassbuf1, SNAKE_GRASS1_PATH, SNAKE_BLOCK_WIDTH, SNAKE_BLOCK_HEIGHT);
  grassbuf2 = malloc(SNAKE_PIC_WIDTH*SNAKE_PIC_HEIGHT*4);
  lcd_bmp_to_Buf(grassbuf2, SNAKE_GRASS2_PATH, SNAKE_BLOCK_WIDTH, SNAKE_BLOCK_HEIGHT);

  /* 申请内存和初始化链表--------------------------- */
  /* 初始化地图 */
  mapbuf = malloc(SNAKE_MAP_WIDTH*SNAKE_MAP_HEIGHT*4);
  int32_t *mapbuf_pos = mapbuf;
  // srand(time(NULL));
  for (u_int32_t i = 0; 
        i < ((SNAKE_MAP_WIDTH/SNAKE_BLOCK_WIDTH)*(SNAKE_MAP_HEIGHT/SNAKE_BLOCK_HEIGHT)); 
          i++)
  {/* 循环的次数中看似复杂，其实就是计算出整个地图里有多少个方块 */
    grassToMap((i%30), (i/30));
  }
  printf("map init finish\n");

  /* 初始化物品链表--------------------------------- */
  data_tmp.num = 0;
  for (int32_t i = 0; i < 4; i++)
  {
    blockGroud_list[i] = createNode(data_tmp);
  }

  /* 初始化方块的固定属性---------------------------- */
  /* 食物 */
  food.num = 20;     /* 总数 */
  food.block_type = SNAKE_FOOD;
  food.direction = SD_NONE;
  food.block_effect1 = SNAKE_GET_SCORE_3;
  food.block_effect2 = SNAKE_LONGER;
  memcpy(food.pic_name, "food", sizeof("food")); 
  /* 毒药 */
  poison.num = 10;
  poison.block_type = SNAKE_POISON;
  poison.direction = SD_NONE;
  poison.block_effect1 = SNAKE_LOSE_SCORE_3;
  poison.block_effect2 = SNAKE_LOSE_HEALTH_1;
  memcpy(poison.pic_name, "poison", sizeof("poison")); 
  /* 墙 */
  wall.num = 30;
  wall.block_type = SNAKE_WALL;
  wall.direction = SD_NONE;
  wall.block_effect1 = SNAKE_LOSE_SCORE_5;
  wall.block_effect2 = SNAKE_LOSE_HEALTH_1;
  memcpy(wall.pic_name, "wall", sizeof("wall")); 
  /* 蛇头 */
  snake_head.block_type = SNAKE_HEAD;
  snake_head.block_effect1 = SNAKE_LOSE_HEALTH_1;
  snake_head.block_effect2 = SNAKE_NONE;
  /* 蛇身 */
  snake_body.block_type = SNAKE_BODY;
  snake_body.block_effect1 = SNAKE_LOSE_HEALTH_1;
  snake_body.block_effect2 = SNAKE_SHORTER;
  /* 蛇尾 */
  snake_tail.block_type = SNAKE_TAIL;
  snake_tail.block_effect1 = SNAKE_LOSE_HEALTH_1;
  snake_tail.block_effect2 = SNAKE_SHORTER;

  /* 生成物品-------------------------------------- */
  /* 食物 */
  for (u_int32_t i = 0; i < food.num; i++)
  {
    generate_goods(SNAKE_FOOD);
  }
  /* 毒药 */
  for (u_int32_t i = 0; i < poison.num; i++)
  {
    generate_goods(SNAKE_POISON);
  }
  /* 墙 */
  for (u_int32_t i = 0; i < wall.num; i++)
  {
    generate_goods(SNAKE_WALL);
  }

  // for (u_int32_t i = 0; i < 4; i++)
  // {
  //   printf("\ngoods list %d\n", i);
  //   ergodicLink(blockGroud_list[i], show_all, 1);    
  // }

  /* 初始化角色————蛇链表--------------------------------- */
  for (int32_t i = 0; i < player_num; i++)
  {
    snake[i] = createNode(data_tmp);

    /* 蛇头 */
    memcpy(&data_tmp, &snake_head, sizeof(datatype_t));
    memcpy(data_tmp.pic_name, "1_4", sizeof("1_4"));
    data_tmp.num = 1;
    data_tmp.b_x = 15;
    data_tmp.b_y = 14;
    /* 下面一行代码是方块所属区块的算法，可能有点绕 */
    data_tmp.block_group = (data_tmp.b_y/SNAKE_BG_HEIGHT)*SNAKE_BG_WIDTH_NUM
                              + (data_tmp.b_x/SNAKE_BG_WIDTH);
    data_tmp.direction  = SD_RIGHT;
    node_tmp = createNode(data_tmp);
    insertTial(snake[i], node_tmp);
    
    /* 蛇身 */
    memcpy(&data_tmp, &snake_body, sizeof(datatype_t));
    memcpy(data_tmp.pic_name, "2_0", sizeof("2_0"));
    data_tmp.num = 2;
    data_tmp.b_x = 14;
    data_tmp.b_y = 14;
    /* 下面一行代码是方块所属区块的算法，可能有点绕 */
    data_tmp.block_group = (data_tmp.b_y/SNAKE_BG_HEIGHT)*SNAKE_BG_WIDTH_NUM
                              + (data_tmp.b_x/SNAKE_BG_WIDTH);
    data_tmp.direction  = SD_NONE;
    node_tmp = createNode(data_tmp);
    insertTial(snake[i], node_tmp);

    /* 蛇尾 */
    memcpy(&data_tmp, &snake_tail, sizeof(datatype_t));
    memcpy(data_tmp.pic_name, "3_4", sizeof("3_4"));
    data_tmp.num = 3;
    data_tmp.b_x = 13;
    data_tmp.b_y = 14;
    /* 下面一行代码是方块所属区块的算法，可能有点绕 */
    data_tmp.block_group = (data_tmp.b_y/SNAKE_BG_HEIGHT)*SNAKE_BG_WIDTH_NUM
                              + (data_tmp.b_x/SNAKE_BG_WIDTH);
    data_tmp.direction  = SD_RIGHT;
    node_tmp = createNode(data_tmp);
    insertTial(snake[i], node_tmp);
  }
  // ergodicLink(snake[0], show_all, 1);    /* 输出蛇的信息 */

  /* 初始化视野的方块坐标 */
  b_view_x = snake[0]->next->data->b_x - SNAKE_GFACE_BLOCK_X;
  b_view_y = snake[0]->next->data->b_y - SNAKE_GFACE_BLOCK_Y;
}

/*******************************************************************************
 * 
 *                                  显示界面相关
 * 
*******************************************************************************/

/**
 * @brief 刷新游戏界面的显示
 * 
 * @param fb_base 显存映射的基地址
 * @param fd      显示屏的文件描述符
 * @param vinfo   显示屏可变属性的指针* 
 */
static void snake_refreshGI (int *fb_base, int fd,
                              struct fb_var_screeninfo *vinfo)
{
  char path_buf[256] = {0};
  char name_buf = 0;
  /* 存储方块图像 */
  int *bmp_buf = malloc(SNAKE_BLOCK_WIDTH*SNAKE_BLOCK_HEIGHT*4);

  /* 在地图中绘制蛇 */
  for (u_int8_t i = 0; i < player_num; i++)
  {
    // for (u_int32_t i = 0; i < player[j].length; i++)
    DCLinkList_t *p = snake[i]->next;
    int cnt = 0;
    while (p != snake[i])
    {
      sprintf(path_buf, "%s%s.bmp", SNAKE_BODY_PATH, p->data->pic_name);
      lcd_bmp_to_Buf(bmp_buf, path_buf, SNAKE_BLOCK_WIDTH, SNAKE_BLOCK_HEIGHT);
      blockToMap(mapbuf, bmp_buf, p->data->b_x, p->data->b_y);
      p = p->next;
    }
  }

  /* 在地图中绘制物品 */
  for (u_int32_t i = 0; i < 4; i++)
  {
    DCLinkList_t *p = blockGroud_list[i]->next;
    while (p != blockGroud_list[i])
    {
      sprintf(path_buf, "%s%s.bmp", SNAKE_GOODS_PATH, p->data->pic_name);
      // printf("%s\n", path_buf);
      lcd_bmp_to_Buf(bmp_buf, path_buf, SNAKE_BLOCK_WIDTH, SNAKE_BLOCK_HEIGHT);
      blockToMap(mapbuf, bmp_buf, p->data->b_x, p->data->b_y);
      p = p->next;
    }
    
  }
  
  
  /* 在地图中绘制草地 */
  
  lcd_show_buf (LCD_DISPLAY(fb_base, view_block), 0, 0, 
                  SNAKE_GAMEFACE_WIDTH, SNAKE_GAMEFACE_HEIGHT, 
                    mapbuf, SNAKE_MAP_WIDTH, 
                      (b_view_y*SNAKE_BLOCK_HEIGHT), (b_view_x*SNAKE_BLOCK_WIDTH));
 
  free(bmp_buf);
}                              

/**
 * @brief 刷新操作界面的显示
 * 
 * @param fb_base 显存映射的基地址
 * @param fd      显示屏的文件描述符
 * @param vinfo   显示屏可变属性的指针* 
 */
static void snake_refreshOI (int *fb_base, int fd,
                              struct fb_var_screeninfo *vinfo)
{
    char buf[256] = {0};
    // LCD_FULL( LCD_DISPLAY(fb_base, view_block), LCD_BLUE );
    // lcd_show_buf(LCD_DISPLAY(fb_base, view_block), 50, 50, 32, 32, grassbuf);

    /* 背景音乐开关 */
    if (bgmHasON)
    {
      lcd_show_bmp_rmColor(LCD_DISPLAY(fb_base, view_block), SNAKE_BGMON_PATH, 
                            SNAKE_BGM_X_START, SNAKE_BGM_Y_START, LCD_WHITE);
    }
    else
    {
      lcd_show_bmp_rmColor(LCD_DISPLAY(fb_base, view_block), SNAKE_BGMOFF_PATH, 
                            SNAKE_BGM_X_START, SNAKE_BGM_Y_START, LCD_WHITE);
    }
    
    /* 暂停开关 */
    if (suspend)
    {
      lcd_show_bmp_rmColor(LCD_DISPLAY(fb_base, view_block), SNAKE_SUSPEND_PATH, 
                            SNAKE_RUN_X_START, SNAKE_RUN_Y_START, LCD_WHITE);
    }
    else
    {
      lcd_show_bmp_rmColor(LCD_DISPLAY(fb_base, view_block), SNAKE_RUN_PATH, 
                            SNAKE_RUN_X_START, SNAKE_RUN_Y_START, LCD_WHITE);
    }

    /* 返回键 */
    lcd_show_bmp_rmColor(LCD_DISPLAY(fb_base, view_block), SNAKE_BACK_PATH, 
                            SNAKE_BACK_X_START, SNAKE_BACK_Y_START, LCD_WHITE);

    /* 玩家1的分数  */
    sprintf(buf, "score:%d", player[0].score);
    DispString_EN (LCD_DISPLAY(fb_base, view_block), SNAKE_SCORE_X_START, 
                    SNAKE_SCORE_Y_START, buf);

    /* 生命值 */
    for (int i = 0; i < 3; i++)
    {
      if (i <= (player[0].health-1))
      {
        lcd_show_bmp_rmColor(LCD_DISPLAY(fb_base, view_block), SNAKE_HEALTH_PATH, 
                              (SNAKE_HEALTH_X_START+i*SNAKE_PIC_WIDTH), 
                                SNAKE_HEALTH_Y_START, LCD_WHITE);
      }
      else
      {
        lcd_show_bmp_rmColor(LCD_DISPLAY(fb_base, view_block), SNAKE_HEALTHOFF_PATH, 
                              (SNAKE_HEALTH_X_START+i*SNAKE_PIC_WIDTH), 
                                SNAKE_HEALTH_Y_START, LCD_WHITE);
      }
      
    }

    /* 上 */
    lcd_show_bmp_rmColor(LCD_DISPLAY(fb_base, view_block), SNAKE_UP_PATH, 
                            SNAKE_UP_X_START, SNAKE_UP_Y_START, LCD_WHITE);

    /* 下 */
    lcd_show_bmp_rmColor(LCD_DISPLAY(fb_base, view_block), SNAKE_DOWN_PATH, 
                            SNAKE_DOWN_X_START, SNAKE_DOWN_Y_START, LCD_WHITE); 

    /* 左 */
    lcd_show_bmp_rmColor(LCD_DISPLAY(fb_base, view_block), SNAKE_LEFT_PATH, 
                            SNAKE_LEFT_X_START, SNAKE_LEFT_Y_START, LCD_WHITE);

    /* 右 */
    lcd_show_bmp_rmColor(LCD_DISPLAY(fb_base, view_block), SNAKE_RIGHT_PATH, 
                            SNAKE_RIGHT_X_START, SNAKE_RIGHT_Y_START, LCD_WHITE);

}                            

static void snake_refreshInterface (int *fb_base, int fd,
                                      struct fb_var_screeninfo *vinfo)
{
  view_block++;
  LCD_CLEAN ( LCD_DISPLAY(fb_base, view_block) );   /* 清屏 */

  snake_refreshOI(fb_base, fd, vinfo);
  snake_refreshGI(fb_base, fd, vinfo);

  LCD_SET_DISPLAY(fd, vinfo, view_block);
}

static SI_KEY snake_backOrRestart_interface (int *fb_base, int fd,
                                                struct fb_var_screeninfo *vinfo)
{
  /* 显示界面 --------------------------------*/
  char buf[20];
  lcd_rectangle (LCD_DISPLAY(fb_base, view_block), SI_X_START, SI_Y_START, 
                    SI_WIDTH, SI_HEIGHT, SI_COLOR);
  sprintf (buf, "score:%d", player[0].score);
  DispString_EN (LCD_DISPLAY(fb_base, view_block), SI_SCORE_X, SI_SCORE_Y, buf);
  lcd_rectangle (LCD_DISPLAY(fb_base, view_block), 
                  SI_RESTART_X_START, SI_RESTART_Y_START, 
                    SI_RESTART_WIDTH, SI_RESTART_HEIGHT, SI_RESTART_COLOR);
  DispString_EN (LCD_DISPLAY(fb_base, view_block), 
                  SI_RESTART_TEXT_X, SI_RESTART_TEXT_Y, "restart");
  lcd_rectangle (LCD_DISPLAY(fb_base, view_block), 
                  SI_EXIT_X_START, SI_EXIT_Y_START, 
                    SI_EXIT_WIDTH, SI_EXIT_HEIGHT, SI_EXIT_COLOR);
  DispString_EN (LCD_DISPLAY(fb_base, view_block), 
                  SI_EXIT_TEXT_X, SI_EXIT_TEXT_Y, "exit");

  if (player[0].health > 0)
  {
    lcd_rectangle (LCD_DISPLAY(fb_base, view_block), 
                    SI_BACK_X_START, SI_BACK_Y_START, 
                      SI_BACK_WIDTH, SI_BACK_HEIGHT, SI_BACK_COLOR);
    DispString_EN (LCD_DISPLAY(fb_base, view_block), 
                    SI_BACK_TEXT_X, SI_BACK_TEXT_Y, "back");
  }
        
  /* 判断操作 -------------------------------- */
  TOUCH *tc = malloc(sizeof(TOUCH));
  SI_KEY key = SI_KEY_NONE;
  while (1)
  {
    get_tcState(tc);
    if (!tc->response)
    {
      int32_t x = tc->x_last;
      int32_t y = tc->y_last;  
      switch (tc->state)
      {
      case STATE_SINGLE_CLICK: 
        response_over();
        if ((x > SI_RESTART_X_START) && (x < SI_RESTART_X_END) 
              && (y > SI_RESTART_Y_START) && (y < SI_RESTART_Y_END) )
        {/* 重新开始 */
          return SI_KEY_RESTART;
        }
        else if ((x > SI_EXIT_X_START) && (x < SI_EXIT_X_END) 
                  && (y > SI_EXIT_Y_START) && (y < SI_EXIT_Y_END) )
        {/* 退出游戏 */
          snake_save_interface (fb_base, fd, vinfo);
          snake_rank_interface (fb_base, fd, vinfo);
          return SI_KEY_EXIT; 
        }
        else if ((player[0].health>0) 
                  && (x > SI_BACK_X_START) && (x < SI_BACK_X_END) 
                    && (y > SI_BACK_Y_START) && (y < SI_BACK_Y_END) )
        {/* 返回游戏 */
          return SI_KEY_BACK;
        }

        break;
      default:
        response_over();
        break;
      }
    }
    
  }
  
}                                              


/*******************************************************************************
 * 
 *                                贪吃蛇的移动
 * 
*******************************************************************************/

/**
 * @brief 蛇的移动函数    
 * 
 * @param snake_player  [out]蛇链表
 * @return DCLinkList_t 返回吃到的东西，没吃到则返回NULL
 */
static DCLinkList_t *snake_move (DCLinkList_t *snake_player)
{
  bool outLine = false;
  SDirection_t dire = snake_player->next->data->direction;

  int8_t  buf[20];
  int32_t b_x_tmp;
  int32_t b_y_tmp;

  DCLinkList_t *eat_block = NULL; /* 吃到的东西 */
  datatype_t   data_tmp;
  DCLinkList_t *new_head;
  DCLinkList_t *old_head = snake_player->next;
  DCLinkList_t *new_tail = snake_player->prev->prev;
  DCLinkList_t *old_tail = snake_player->prev;

  /* 1. 创建一个新的蛇头节点并头插法插入------------------------------- */
  memcpy(&data_tmp, old_head->data, sizeof(datatype_t));

  /* 更新蛇头方向 */
  data_tmp.direction = dire;

  /* 更新蛇头坐标 */
  switch (data_tmp.direction)
  {
  case SD_UP:
    if (data_tmp.b_y > 0)
    {
      data_tmp.b_y--;
    }
    else
    {
      outLine = true;
    }
    break;
  case SD_DOWN:
    if (data_tmp.b_y < SNAKE_BG_HEIGHT*SNAKE_BG_HEIGHT_NUM-1) 
    {
      data_tmp.b_y++;
    }
    else
    {
      outLine = true;
    }
    break;
  case SD_LEFT:
    if (data_tmp.b_x > 0)
    {
      data_tmp.b_x--;
    }
    else
    {
      outLine = true;
    }
    break;
  case SD_RIGHT:
    if (data_tmp.b_x < SNAKE_BG_WIDTH*SNAKE_BG_WIDTH_NUM-1) 
    {
      data_tmp.b_x++;
    }
    else
    {
      outLine = true;
    }
    break;
  
  default:
    break;
  }

  /* 蛇要出界了就不刷新 */
  if (outLine)
  {
    return NULL;
  }

  /* 更新蛇头所属区块 */
  data_tmp.block_group = (data_tmp.b_y/SNAKE_BG_HEIGHT)*SNAKE_BG_WIDTH_NUM
                              + (data_tmp.b_x/SNAKE_BG_WIDTH);

  /* 更新图片名 */                              
  sprintf(buf, "%d_%d", data_tmp.block_type, data_tmp.direction);
  memcpy(data_tmp.pic_name, buf, strlen(buf));
  *(data_tmp.pic_name + strlen(buf) + 1) = '\0';

  new_head = createNode(data_tmp);
  insertHead(snake_player, new_head);

  /* 2. 将旧的蛇头节点(换成身子)---------------------------------------- */  
  /* 先暂存坐标值 */
  b_x_tmp = old_head->data->b_x;
  b_y_tmp = old_head->data->b_y;
  
  /* 将旧的蛇头的下一个身子节点的数据复制过来 */
  memcpy(old_head->data, old_head->next->data, sizeof(datatype_t));
  
  /* 恢复坐标 */
  old_head->data->b_x = b_x_tmp;
  old_head->data->b_y = b_y_tmp;

  /* 对于判断是否吃到蛇只判断是否吃到自己，没有判断是否吃到其它蛇 */
  eat_block = eat_something(*(new_head->data), snake_player);
  if (eat_block != NULL)
  {
    printf("吃到东西了！\n");
    show_all(*(eat_block->data));
  }

  /* 没吃到东西，蛇的长度不变 */
  if (!((eat_block != NULL) && (eat_block->data->block_effect2 == SNAKE_LONGER)))
  {
    if ((eat_block != NULL) && (eat_block->data->block_type == SNAKE_TAIL))
    {
      printf("eat tail\n");
      eat_block = NULL;
    }
    
    /* 3. 将倒数第二个节点换成新的蛇尾------------------------------------- */
    /* 先暂存坐标值 */
    b_x_tmp = new_tail->data->b_x;
    b_y_tmp = new_tail->data->b_y;

    /* 将旧的蛇尾的数据复制给新蛇尾 */
    memcpy(new_tail->data, old_tail->data, sizeof(datatype_t));

    /* 恢复坐标 */
    new_tail->data->b_x = b_x_tmp;
    new_tail->data->b_y = b_y_tmp;

    /* 判断新蛇尾的方向 */
    int32_t delta_x, delta_y;
    delta_x = new_tail->prev->data->b_x - new_tail->data->b_x;
    delta_y = new_tail->prev->data->b_y - new_tail->data->b_y;
    if      ((delta_x == 0) && (delta_y > 0))   /* 向下 */
    {
      new_tail->data->direction = SD_DOWN;
    }
    else if ((delta_x == 0) && (delta_y < 0))   /* 向上 */
    {
      new_tail->data->direction = SD_UP;
    }
    else if ((delta_x > 0) && (delta_y == 0))   /* 向右 */
    {
      new_tail->data->direction = SD_RIGHT;
    }
    else if ((delta_x < 0) && (delta_y == 0))   /* 向左 */
    {
      new_tail->data->direction = SD_LEFT;
    }

    /* 更新新蛇尾的文件名 */
    sprintf(buf, "%d_%d", new_tail->data->block_type, new_tail->data->direction);
    memcpy(new_tail->data->pic_name, buf, strlen(buf));
    *(new_tail->data->pic_name + strlen(buf) + 1) = '\0';

    /* 4. 将旧的蛇尾去掉------------------------------------------------- */
    grassToMap(new_tail->data->b_x, new_tail->data->b_y);
    grassToMap(old_tail->data->b_x, old_tail->data->b_y);
    deleteNode(old_tail);
  }

  /* 更新视野的方块坐标 */
  b_view_x = new_head->data->b_x - SNAKE_GFACE_BLOCK_X;
  b_view_y = new_head->data->b_y - SNAKE_GFACE_BLOCK_Y;

  /* 限制范围 */
  b_view_x = (b_view_x < SNAKE_GAMEFACE_B_X_START)
                ? SNAKE_GAMEFACE_B_X_START : b_view_x;
  b_view_x = ((b_view_x + SNAKE_GAMEFACE_BLOCK_WIDTH) > SNAKE_GAMEFACE_B_X_END)
                ? (SNAKE_GAMEFACE_B_X_END - SNAKE_GAMEFACE_BLOCK_WIDTH) : b_view_x;
  b_view_y = (b_view_y < SNAKE_GAMEFACE_B_Y_START)
                ? SNAKE_GAMEFACE_B_Y_START : b_view_y;
  b_view_y = ((b_view_y + SNAKE_GAMEFACE_BLOCK_HEIGHT) > SNAKE_GAMEFACE_B_Y_END)
                ? (SNAKE_GAMEFACE_B_Y_END - SNAKE_GAMEFACE_BLOCK_HEIGHT) : b_view_y;

  if (eat_block != NULL)
  {
    printf("In move fun\n");
    show_all(*(eat_block->data));
  }
  return eat_block;
}

/**
 * @brief 判断蛇吃到了什么东西
 * 
 * @param snakeHead_data  蛇头节点
 * @param snake           蛇的链表，用来判断是否吃到了自己
 * @return DCLinkList_t* 吃到则返回吃到的方块的地址指针，没吃到返回 NULL
 */
static DCLinkList_t *eat_something(datatype_t snakeHead_data, DCLinkList_t *snake)
{
  DCLinkList_t *eatBlock = NULL;
  /* 根据蛇头所在区块确定遍历哪个物品链表 */
  DCLinkList_t *goods_p = blockGroud_list[snakeHead_data.block_group];  
  DCLinkList_t *snake_p = snake;  /* 跳过蛇头的判断 */

  /* 判断是否吃到蛇 */
  eatBlock = searchNode(snake_p, snakeHead_data, eat_snake);
  if (eatBlock != NULL)
  {
    return eatBlock;
  }
  
  /* 判断是否吃到物品 */
  eatBlock = searchNode(goods_p, snakeHead_data, eat_goods);

  return eatBlock;
}


/**
 * @brief 触发方块效果
 *  
 * @param snake     蛇链表 
 * @param py        玩家结构体
 * @param eatBlock  被吃到的方块
 */
static void trigger_block(DCLinkList_t *snake, SnakePlayer_t *py, DCLinkList_t *eatBlock)
{
  if (eatBlock != NULL)
  {
    bool eat_goods = false;
    bool eat_self = false;
    printf("In fun trigger\n");
    show_all(*(eatBlock->data));
    switch (eatBlock->data->block_type)
    {
    case SNAKE_FOOD:    /* 1：加分  2：变长 */
      py->score  += eatBlock->data->block_effect1;
      py->length++;
      eat_goods = true;
      break;
    case SNAKE_POISON:  /* 1：扣分  2：减生命 */
      py->score  += eatBlock->data->block_effect1;
      py->health += eatBlock->data->block_effect2;  /* 减生命值 */
      eat_goods = true;
      break;
    case SNAKE_WALL:    /* 1：扣分  2：减生命 */
      py->score  += eatBlock->data->block_effect1;
      py->health += eatBlock->data->block_effect2;  /* 减生命值 */
      eat_goods = true;
      break;
    case SNAKE_BODY:    /* 1：减生命  2：变短 */
      py->health += eatBlock->data->block_effect1;  /* 减生命值 */
      eat_self = true;
      break;
    
    default:
      break;
    }

    if (eat_goods)
    { 
      printf("refresh goods\n");   
      generate_goods(eatBlock->data->block_type); /* 在链表中生成新物品 */
                              /* 新的物品图像会在刷新游戏界面时刷新所以不需要在这里刷新 */
      grassToMap(eatBlock->data->b_x, eatBlock->data->b_y); /* 在地图中删掉旧物品 */
      deleteNode(eatBlock);                       /* 在链表中删除旧物品 */
    }
    else if (eat_self)
    {
      /* 记录初始长度（计算扣分用） */
      u_int32_t length_tmp = py->length;  
      DCLinkList_t *new_tail = eatBlock->prev;

      /* 剪掉被吃掉的部分 */
      DCLinkList_t *tmp = eatBlock;
      eatBlock = eatBlock->next;;
      while (tmp != snake)
      {
        grassToMap(tmp->data->b_x, tmp->data->b_y); /* 将图像用草房快覆盖掉 */
        deleteNode(tmp);
        py->length--;
        tmp = eatBlock;
        eatBlock = eatBlock->next;
      }
      eatBlock = NULL;

      /* 按吃掉多少的百分比扣分 */
      py->score = py->score * py->length / length_tmp;

      /* 更新尾巴 */
      int32_t b_x_tmp =  new_tail->data->b_x;
      int32_t b_y_tmp =  new_tail->data->b_y;
      memcpy(new_tail->data, &snake_tail, sizeof(datatype_t));
      new_tail->data->b_x = b_x_tmp;
      new_tail->data->b_y = b_y_tmp;

      /* 判断新蛇尾方向 */
      int32_t delta_x, delta_y;
      delta_x = new_tail->prev->data->b_x - new_tail->data->b_x;
      delta_y = new_tail->prev->data->b_y - new_tail->data->b_y;
      if      ((delta_x == 0) && (delta_y > 0))   /* 向下 */
      {
        new_tail->data->direction = SD_DOWN;
      }
      else if ((delta_x == 0) && (delta_y < 0))   /* 向上 */
      {
        new_tail->data->direction = SD_UP;
      }
      else if ((delta_x > 0) && (delta_y == 0))   /* 向右 */
      {
        new_tail->data->direction = SD_RIGHT;
      }
      else if ((delta_x < 0) && (delta_y == 0))   /* 向左 */
      {
        new_tail->data->direction = SD_LEFT;
      }

      /* 更新新蛇尾的文件名 */
      char buf[8];
      sprintf(buf, "%d_%d", new_tail->data->block_type, new_tail->data->direction);
      memcpy(new_tail->data->pic_name, buf, strlen(buf));
      *(new_tail->data->pic_name + strlen(buf) + 1) = '\0';

    }
    

    if (py->score < 0)
    {
      py->score = 0;
    }
    
    // sleep(5);
  }
    
}

/**
 * @brief Get the Key object
 * 
 * @param x 
 * @param y 
 * @return SnakeKey_t 
 */
static SnakeKey_t getKey (int32_t x, int32_t y)
{
  /* 上 */
  if ( (!suspend) && (x > SNAKE_UP_X_START) && (x < SNAKE_UP_X_END)
      && (y > SNAKE_UP_Y_START) && (y < SNAKE_UP_Y_END))
  {
    return SNAKE_KEY_UP;
  }

  /* 下 */
  else if ((!suspend) && (x > SNAKE_DOWN_X_START) && (x < SNAKE_DOWN_X_END)
            && (y > SNAKE_DOWN_Y_START) && (y < SNAKE_DOWN_Y_END))
  {
    return SNAKE_KEY_DOWN;
  }

  /* 左 */
  else if ((!suspend) && (x > SNAKE_LEFT_X_START) && (x < SNAKE_LEFT_X_END)
            && (y > SNAKE_LEFT_Y_START) && (y < SNAKE_LEFT_Y_END))
  {
    return SNAKE_KEY_LEFT;
  }

  /* 右 */
  else if ((!suspend) && (x > SNAKE_RIGHT_X_START) && (x < SNAKE_RIGHT_X_END)
            && (y > SNAKE_RIGHT_Y_START) && (y < SNAKE_RIGHT_Y_END))
  {
    return SNAKE_KEY_RIGHT;
  }

  /* BGM */
  else if ((x > SNAKE_BGM_X_START) && (x < SNAKE_BGM_X_END)
            && (y > SNAKE_BGM_Y_START) && (y < SNAKE_BGM_Y_END))
  {
    return SNAKE_KEY_BGM;
  }

  /* 暂停/继续 */
  else if ((x > SNAKE_RUN_X_START) && (x < SNAKE_RUN_X_END)
            && (y > SNAKE_RUN_Y_START) && (y < SNAKE_RUN_Y_END))
  {
    return SNAKE_KEY_SUSPEND;
  }

  /* 返回 */
  else if ((x > SNAKE_BACK_X_START) && (x < SNAKE_BACK_X_END)
            && (y > SNAKE_BACK_Y_START) && (y < SNAKE_BACK_Y_END))
  {
    return SNAKE_KEY_BACK;
  }

  return SNAKE_KEY_NONE;
}


/**
 * @brief 内联函数，将一个草方块填到地图上
 * 
 * @param b_map_x 要填的方块x坐标
 * @param b_map_y 要填的方块y坐标
 */
static inline void grassToMap (int32_t b_map_x, int32_t b_map_y) 
{
  if (rand()%4 > 2)
  {
    blockToMap (mapbuf, grassbuf2, b_map_x, b_map_y);
  }
  else
  {
    blockToMap (mapbuf, grassbuf1, b_map_x, b_map_y);
  }
}

/**
 * @brief 随机生成物品
 * 
 * @param goods 想要生成的物品
 */
static void generate_goods (SBlockType_t goods) 
{
  DCLinkList_t *new_goods;
  datatype_t data;
  
  /* 先获取固定属性 */
  switch (goods)
  {
  case SNAKE_FOOD:
    memcpy(&data, &food, sizeof(datatype_t));
    break;
  case SNAKE_POISON:
    memcpy(&data, &poison, sizeof(datatype_t));
    break;
  case SNAKE_WALL:
    memcpy(&data, &wall, sizeof(datatype_t));
    break;

  default:
    break;
  }

  /* 生成随机属性 */
  data.b_x = rand()%(SNAKE_BG_WIDTH * SNAKE_BG_WIDTH_NUM);
  data.b_y = rand()%(SNAKE_BG_HEIGHT * SNAKE_BG_HEIGHT_NUM);
  data.block_group = (data.b_y/SNAKE_BG_HEIGHT)*SNAKE_BG_WIDTH_NUM
                              + (data.b_x/SNAKE_BG_WIDTH);
  new_goods = createNode(data);
  insertHead(blockGroud_list[data.block_group], new_goods);
}

/* ---------------------------------给链表提供的接口--------------------------- */
/**
 * @brief 打印节点数据
 * 
 * @param data 要打印的数据
 */
void show_all(datatype_t data)
{
  printf("--------------------------------\n");
  printf("No:%d\tpic:%s\n", data.num, data.pic_name);
  printf("block type:%d\tdirection:%d\n", data.block_type, data.direction);
  printf("x:%d,y:%d,bg:%d\n", data.b_x, data.b_y, data.block_group);
  printf("effect1:%d\teffect2:%d\n", data.block_effect1, data.block_effect2);
}

/**
 * @brief 对比坐标判断是否吃到物品
 * 
 * @param a       要对比的数据
 * @param s_head  蛇头的数据
 * @return true   吃到
 * @return false  没吃到
 */
bool eat_goods(datatype_t a, datatype_t s_head)
{
  if ((a.b_x == s_head.b_x) && (a.b_y == s_head.b_y))
  {
    return true;
  }
  else
  {
    return false;
  }
}

/**
 * @brief 对比坐标判断是否吃到蛇，与对比物品不同的是判断蛇还要先判断蛇节点所在的区块是否是同一个
 *        如果区块不在同一个那就没必要继续判断坐标了
 * @param a       要对比的数据
 * @param s_head  蛇头的数据
 * @return true   吃到
 * @return false  没吃到
 */
bool eat_snake(datatype_t a, datatype_t s_head)
{
  if ((a.block_type != SNAKE_HEAD)  &&  /* 要对比的数据不是蛇头（不然就是蛇头吃自己的蛇头）*/
          (a.b_x == s_head.b_x) && (a.b_y == s_head.b_y)) /* 对比方块坐标 */
  {
    return true;
  }
  else
  {
    return false;
  }
}

/*------------------------------ end of file --------------------------------*/

